package my.SimulatorGUI1;

import java.util.LinkedList;
import my.util.Handler;
import my.util.Debug;


/**
 *
 * @author rp012053
 */

/* 
 * The LogicController spawns Timer objects and handles messages from them.  It
 * also manipulates the GUI.  It represents the state of the hardware with two
 * private classes, WarnError and HWStates
 * 
 * Yours Truly,
 * jda99448
 */
public class LogicController implements Handler{
    int count=0;
    // ****** Begin Warning and Error definition ******
    //constant int error and warning integer values (also array indices)
    public static final int ERR_NO_INSULIN = 0;
    public static final int ERR_PUMP_MALFUNCTION = 1;
    public static final int ERR_RESERVOIR_MISSING = 2;
    public static final int ERR_SUGAR_SENSOR_MALF = 3;
    public static final int ERR_NEEDLE_OUT = 4;
    public static final int WARN_LOW_BATTERY = 5;
    public static final int WARN_LOW_INSULIN = 6;
    public static final int WARN_MAX_DAILY_DOSE = 7;
    public static final int WARN_MAX_SINGLE_DOSE = 8;
    public static final int WARN_LOW_BLOOD_SUGAR = 9;

    // ***** fencepost constants for warnings, errors, and system reset *****
    private static final int RESET_TIME = 720;
    private static final int MAXDAILYDOSE = 35;
    private static final int MAXSINGLEDOSE = 5;
    private static final int MINSINGLEDOSE = 1;
    private static final int UNSAFE = 3;
    private static final int UNDESIRABLE = 7;
    private static final int LOW_INSULIN = 20;
    private static final int LOW_BATTERY = 33;

    int currentWarnErrorIndex = 0;
    boolean MAX_SINGLE_DOSE_EXCEEDED = false;

    /* called by Handler.handle(String, boolean) if the message begins with
     * "ERR_" or "WARN_" */
    private void handleWarnError(String message, boolean b) {
        if (message.equals("ERR_NO_INSULIN")) 
            messageList[ERR_NO_INSULIN].active = b;
        if (message.equals("ERR_PUMP_MALFUNCTION"))
            messageList[ERR_PUMP_MALFUNCTION].active = b;
        if (message.equals("ERR_RESERVOIR_MISSING"))
            messageList[ERR_RESERVOIR_MISSING].active = b;
        if (message.equals("ERR_SUGAR_SENSOR_MALF"))
            messageList[ERR_SUGAR_SENSOR_MALF].active = b;
        if (message.equals("ERR_NEEDLE_OUT"))
            messageList[ERR_NEEDLE_OUT].active = b;
        if (message.equals("WARN_LOW_BATTERY"))
            messageList[WARN_LOW_BATTERY].active = b;
        if (message.equals("WARN_LOW_INSULIN"))
            messageList[WARN_LOW_INSULIN].active = b;
        if (message.equals("WARN_MAX_DAILY_DOSE"))
            messageList[WARN_MAX_DAILY_DOSE].active = b;
        if (message.equals("WARN_MAX_SINGLE_DOSE"))
            messageList[WARN_MAX_SINGLE_DOSE].active = b;
        if (message.equals("WARN_LOW_BLOOD_SUGAR"))
            messageList[WARN_LOW_BLOOD_SUGAR].active = b;
    }

    /* WarnErrors encapsulate the error and warning states of the insulin pump,
     * and the message to be sent to the gui when each is active */
    private class WarnError {
        public boolean active;
        public String message;
        
        public WarnError (String s){
            this.message = s;
            this.active = false;
        }
    }
    
    private WarnError[] messageList; //the warning and error states of the pump
    private void warnErrorInit() {
        messageList = new WarnError[10];
        messageList[ERR_NO_INSULIN] = new WarnError("ERROR: No Insulin. ");
        messageList[ERR_PUMP_MALFUNCTION] = new WarnError("ERROR: Insulin Pump Failure. ");
        messageList[ERR_RESERVOIR_MISSING] = new WarnError("ERROR: Insulin reservoir removed. ");
        messageList[ERR_SUGAR_SENSOR_MALF] = new WarnError("ERROR: Sensor Failure. ");
        messageList[ERR_NEEDLE_OUT] = new WarnError("ERROR: No needle Unit. ");
        messageList[WARN_LOW_BATTERY] = new WarnError("WARNING: Low Battery. ");
        messageList[WARN_LOW_INSULIN] = new WarnError("WARNING: Low Insulin. ");
        messageList[WARN_MAX_DAILY_DOSE] = new WarnError("WARNING: Maximum Daily Dose. ");
        messageList[WARN_MAX_SINGLE_DOSE] = new WarnError("WARNING: Maximum Single Dose. ");
        messageList[WARN_LOW_BLOOD_SUGAR] = new WarnError("WARNING: Low Blood Sugar. ");
    }
    
    public void updateWarnError(){
        if (theHardware.powerOn){
            // ***** Insulin level checking *****
            if (theHardware.insulinLevel < 1)
                messageList[LogicController.ERR_NO_INSULIN].active = true;
            else if (theHardware.insulinLevel < LogicController.LOW_INSULIN)
                messageList[LogicController.WARN_LOW_INSULIN].active = true;
            else {
                messageList[LogicController.ERR_NO_INSULIN].active = false;
                messageList[LogicController.WARN_LOW_INSULIN].active = false;
            }
            
            /* ****** BSL Checking ******
             * Only updates if BSL >= 0.  Negative BSL indicates that a manual
             * dose was just given */
            if(this.timeSamples.getLast().getBloodSugar() >= 0){
                if (this.timeSamples.getLast().getBloodSugar() < LogicController.UNSAFE)
                    messageList[LogicController.WARN_LOW_BLOOD_SUGAR].active = true;
                else
                    messageList[LogicController.WARN_LOW_BLOOD_SUGAR].active = false;
            }
            // ***** Battery Checking *****
            if (theHardware.battery < LOW_BATTERY)
                messageList[LogicController.WARN_LOW_BATTERY].active = true;
            else
                messageList[LogicController.WARN_LOW_BATTERY].active = false;

            // ***** Max. daily dose checking *****
            if (theHardware.accumulatedDose >= MAXDAILYDOSE) 
                messageList[LogicController.WARN_MAX_DAILY_DOSE].active = true;
            else
                messageList[LogicController.WARN_MAX_DAILY_DOSE].active = false;
                        
            // ***** Max. single dose checking *****
            //TODO: Verify
            /* only give warning if max single dose exceeded. 
             * only possible in manual mode */
            if (MAX_SINGLE_DOSE_EXCEEDED)
                messageList[LogicController.WARN_MAX_SINGLE_DOSE].active = true;
            else
                messageList[LogicController.WARN_MAX_SINGLE_DOSE].active = false;          
        }
    }
    public void displayNextWarnError(){
        boolean bError, bWarn;
        if (theHardware.powerOn){
            updateWarnError();
            bError = hasError();
            bWarn = hasWarning();
            //no errors or warnings
            if (!bError && !bWarn ) {
                theGUI.setOKLight(true);
                theGUI.setWarnLight(false);
                theGUI.setErrorLight(false);
            }
            //Warning but no error
            else if (!bError && bWarn){
              theGUI.setOKLight(false);
              theGUI.setWarnLight(true);
              theGUI.setErrorLight(false);
              java.awt.Toolkit.getDefaultToolkit().beep();
            }
            //Error but no warning
            else if (bError && !bWarn ) {
              theGUI.setOKLight(false);
              theGUI.setWarnLight(false);
              theGUI.setErrorLight(true);
              java.awt.Toolkit.getDefaultToolkit().beep();
            }
            //Errors and warnings
            else if (bError && bWarn ) {
              theGUI.setOKLight(false);
              theGUI.setWarnLight(true);
              theGUI.setErrorLight(true);
              java.awt.Toolkit.getDefaultToolkit().beep();
            }
            
            // ***** Scan for the next active warning or error *****
            /* first, scan forward from current index until we've wrapped around
             * or found an active warning/error */
            int size = messageList.length;
            for (int i = 1; i <= size; i++){
                // if we find an active WarnError, move the bookmark up
                if (messageList[(currentWarnErrorIndex + i) % size].active){
                    currentWarnErrorIndex = (currentWarnErrorIndex + i) % size;
                    break;
                }
            }
            // now check to see if we ended on an active error
            if (messageList[currentWarnErrorIndex].active)
                theGUI.setLblError(messageList[currentWarnErrorIndex].message);
            else //clear the Warning/Error caption if we don't have a message
                theGUI.setLblError("");
        }
        else // if the power is off make sure the error/warning field is clear.
            theGUI.setLblError("");
    }
    
    public boolean hasError(){
        boolean bReturn = false;
        if(messageList[LogicController.ERR_NEEDLE_OUT].active ||
           messageList[LogicController.ERR_NO_INSULIN].active ||
           messageList[LogicController.ERR_PUMP_MALFUNCTION].active ||
           messageList[LogicController.ERR_RESERVOIR_MISSING].active ||
           messageList[LogicController.ERR_SUGAR_SENSOR_MALF].active) {
            bReturn =  true;
        }
        else 
            bReturn  = false;
        return bReturn;
    }
    
    public boolean hasWarning(){
        boolean bReturn = false;
        if(messageList[LogicController.WARN_LOW_BATTERY].active ||
           messageList[LogicController.WARN_LOW_BLOOD_SUGAR].active ||
           messageList[LogicController.WARN_LOW_INSULIN].active ||
           messageList[LogicController.WARN_MAX_DAILY_DOSE].active ||
           messageList[LogicController.WARN_MAX_SINGLE_DOSE].active) {
            bReturn =  true;
        }
        else 
            bReturn  = false;
        return bReturn;
    }
    // ***** End of Error and Warning definition *****

    /* Hardware states are encapsulated in this private class where they can be
     * instantiated and referecned easily */
    private class HWStates{
        public int battery;
        public int insulinLevel;
        public boolean powerOn;
        public int accumulatedDose;
        
        public HWStates() {
            this.battery = 100;
            this.insulinLevel = 100;
            this.powerOn = false;
            this.accumulatedDose = 0;
        }
    }
    private HWStates theHardware;
    private SimulatorGUI1 theGUI;

    // ***** Timers *****
    private Timer warnErrorDisplayTimer;
    private Timer resetTimer;
    private Timer clockTickTimer;
    private Timer autoTimer;    
    private Timer manualTimer;
    private Timer batteryTimer;
    
    private int tickClock;
    private float currentBSL=5;
    private LinkedList<Sample> timeSamples;
    private int manualDoseAmount=0;
    

    public LogicController(){
        theGUI = new SimulatorGUI1(this);
        theGUI.setVisible(true);
        clockTickTimer = new Timer("TICK",1,this);
        timeSamples = new LinkedList<Sample>();
        tickClock=0;
        warnErrorInit();
    }
    
    public void init(){
        theHardware = new HWStates();
        Sample current = new Sample((System.currentTimeMillis()/1000), 5, 1, theHardware.insulinLevel);
        timeSamples.add(current);
        timeSamples.add(current);
        timeSamples.add(current);
        theGUI.displayBSL(timeSamples);
        theGUI.setLblCurrentBattery(Integer.toString(theHardware.battery)); //should this be in an init functio
        autoTimer.gag();
        theHardware.powerOn = true;
        theGUI.setChkNeedle(true);
        theGUI.setChkPump(true);
        theGUI.setChkReservoir(true);
        theGUI.setChkSensor(true);
        displayNextWarnError();
        currentWarnErrorIndex = 0;
        currentBSL = 5;
        tickClock = 0;
    }
    
    private void createTimers(){
        resetTimer = new Timer ("RESET", RESET_TIME, this);
        autoTimer =  new Timer ("AUTO", 10, this);
        batteryTimer =  new Timer ("BATTERY", 30, this);
        warnErrorDisplayTimer = new Timer("NEXT_WARN_ERROR", 3, this);
    }
    
    private void killTimers(){
        resetTimer.die();
        autoTimer.die();
        batteryTimer.die();
        warnErrorDisplayTimer.die();
        resetTimer.gag();
        autoTimer.gag();
        batteryTimer.gag();
        warnErrorDisplayTimer.gag();
    }
    
    public LinkedList getSamples() {
        return timeSamples;
    }
    
    public void handle(String message, float num){
        if(message.equals("BLOOD_SUGAR")){
            currentBSL = num;
            autoTimer.ungag();
        }
        if(message.equals("BATTERY_LEVEL")){
            this.theHardware.battery = (int) num;
        }
        if(message.equals("INSULIN_LEVEL")){
            this.theHardware.insulinLevel = (int) num;
        }
        
    }
    public void handle(String message, boolean b){
        if (message.contains("ERR_") || message.contains("WARN_")) {
            handleWarnError(message, b);
        }
    }
    public void handle(String message){
        if(message.equals("RESET")){
            timeSamples.clear();
            this.killTimers();
            this.createTimers();
            this.init();
        }
        else if(message.equals("TICK")){
            
            theGUI.setCurrentTime();
            if(theGUI.enabled==true) {
                theGUI.setResetTime(tickClock);
                tickClock++;
            }
        }
        else if(message.equals("POWERON")){
            this.createTimers();
            this.init();
            theGUI.powerOnDose = true;
            theHardware.powerOn = true;
            tickClock=0;
            String sBattery;
        }
        else if(message.equals("POWEROFF")){
            theHardware.powerOn = false;
            killTimers();
            timeSamples.clear();
            theGUI.displayBSL(timeSamples);
            theGUI.setLblCurrentBattery("");
            theGUI.resetHardwareCheckBoxes();
            theGUI.setOKLight(false);
            theGUI.setWarnLight(false);
            theGUI.setErrorLight(false);
        }
        else if(message.equals("AUTO") &&  !hasError()){
            theGUI.powerOnDose = false; 
            MAX_SINGLE_DOSE_EXCEEDED = false;
            long ct = System.currentTimeMillis()/1000;
            long lit =0;
            int lid = 0;
            boolean flag = false;
            for(int i=timeSamples.size()-1;i>=0 && !flag;i--){
                if(timeSamples.get(i).getInsulinDose() != 0){
                    lid = timeSamples.get(i).getInsulinDose();
                    lit = timeSamples.get(i).getTime();
                    flag = true;
                }
            }
            
            Debug.println("LID = " + lid);
            float[] bsl = new float[2];
            int i=0,j=timeSamples.size()-1;
            while(i<2){
                while(timeSamples.get(j).getBloodSugar() < 0)
                    j--;
                bsl[i++] = timeSamples.get(j--).getBloodSugar();
            }
            float previousBSL = bsl[0];
            float previousPreviousBSL = bsl[1];
            int compDose=0;
            if ((currentBSL >= UNSAFE) && (currentBSL <= UNDESIRABLE)) {
                //sugar level stable or falling
                Debug.println("SAFE");
                if (currentBSL <= previousBSL){
                    compDose = 0;
                }
                // sugar level increasing but rate of increasing falling
                else if ((currentBSL > previousBSL) && ((currentBSL - previousBSL) < (previousBSL - previousPreviousBSL))){
                    compDose = 0;
                }
                // sugar level increasing and rate of increase increasing, compute dose
                // a minimum dose must be delivered if rounded to zero
                else if ((currentBSL > previousBSL) && ((currentBSL - previousBSL) >= (previousBSL - previousPreviousBSL)) && (Math.round((currentBSL - previousBSL) / 4) > 0)){
                    compDose = Math.round((currentBSL - previousBSL) / 4);
                }
                else if ((currentBSL > previousBSL) && ((currentBSL - previousBSL) >= (previousBSL - previousPreviousBSL)) && ((Math.round((currentBSL - previousBSL) / 4) == 0))){
                    compDose = MINSINGLEDOSE;
                }
            }else if(currentBSL > UNDESIRABLE){
                //sugar level increasing
                Debug.println("UNDESIRABLE");
                if((currentBSL > previousBSL) && (Math.round((currentBSL - previousBSL)/4)==0)){
                    Debug.println("SINGLE DOSE - INCREASING");
                    compDose = MINSINGLEDOSE;
                }
                else if((currentBSL > previousBSL) && (Math.round((currentBSL - previousBSL)/4) > 0)){
                    compDose = Math.round((currentBSL - previousBSL)/4);
                    if(compDose>MAXSINGLEDOSE){
                        MAX_SINGLE_DOSE_EXCEEDED = true;
                        compDose = MAXSINGLEDOSE;
                    }
                Debug.println("DOSE = " + compDose + " - INCREASING");
                    }
                //sugar level stable
                else if(currentBSL == previousBSL){
                    Debug.println("SINGLE DOSE - STABLE");
                    
                    compDose = MINSINGLEDOSE;
                }
                //sugar level falling and rate of decrease increasing
                else if((currentBSL < previousBSL) && ((currentBSL - previousBSL) <= (previousBSL - previousPreviousBSL))){
                    Debug.println("DOSE = 0 - DECREASING");
                    compDose = 0;
                }
                //sugar level falling and rate of decrease decreasing
                else if((currentBSL < previousBSL) && ((currentBSL - previousBSL) > (previousBSL - previousPreviousBSL))){
                    Debug.println("SINGLE DOSE - DECREASING");
                    compDose = MINSINGLEDOSE;
                }
            }
            
            theHardware.insulinLevel -= compDose;
            theHardware.accumulatedDose += compDose;
            Sample newSample = new Sample((System.currentTimeMillis()/1000), currentBSL, compDose, theHardware.insulinLevel);
            timeSamples.add(newSample);
            theGUI.displayBSL(timeSamples);
            currentBSL = (float)(currentBSL - 3* Math.pow(Math.E, -((ct - lit)/lid)*0.04));
            if(currentBSL<0)
                currentBSL =0;
        }
        else if(message.equals("START_MANUAL") && !hasError()){
            theGUI.powerOnDose = false;
            theGUI.giveDoseGag();
            autoTimer.gag();
            manualDoseAmount =0;
            manualTimer = new Timer("END_MANUAL",5,this);
        }else if(message.equals("END_MANUAL")){
            int dose;
            manualTimer.die();
            theGUI.giveDoseGag();
            autoTimer.ungag();
            dose = manualDoseAmount;
            Debug.println("DOSE = " + dose);
            if(dose >0){
                manualDoseAmount =0;
                if(dose > theHardware.insulinLevel)
                    dose = theHardware.insulinLevel;
                if(dose > MAXSINGLEDOSE)
                    MAX_SINGLE_DOSE_EXCEEDED = true;
                else
                    MAX_SINGLE_DOSE_EXCEEDED = false;
                theHardware.insulinLevel -= dose;
                Sample newSample = new Sample((System.currentTimeMillis()/1000), -1, dose, theHardware.insulinLevel);
                timeSamples.add(newSample);
                theGUI.displayBSL(timeSamples);
            }
        }
        else if(message.equals("GIVE_DOSE_CLICK")){
            manualDoseAmount++;
        }
    
        else if (message.equals("NEXT_WARN_ERROR")){
            displayNextWarnError();
        }
        
        else if(message.equals("BATTERY")){
            theHardware.battery -= 4;
            theGUI.setLblCurrentBattery(Integer.toString(theHardware.battery));
        }
    }    
     public static void main (String args[]){
         // checking args, nothing to see here
         for (int i = 0; i < args.length; i++){
             if (args[i].equals("-d") || args[i].equals("-D"))
                 Debug.enabled = true;
         }
         LogicController myController = new LogicController();
         Debug.println("\nMAIN");
    }
}
